﻿using DotNetCommon;
using DotNetCommon.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DBUtil.Provider.MySql;

#region mysql 表名规则总结
//参照: https://blog.csdn.net/u010476739/article/details/152324976
// 总结: 当 mysql 解析的时候,
// 当开头遇到了 '`', 则开启了分割边界
//     接着往后走, 只要不遇到 '`' 就表示是表名(即使是遇到换行符或反斜杠)
//     直到遇到 '`' 则再往后看一个
//         如果又是 '`' 则合并 '``' 为一个普通的 '`'
//         如果不是 '`' 则结束了分割边界
//
// 看下面的实战演示:
//-- 正常情况
//create table `test`(id int) -- test
//
//create table `te\t\r\nst123`(id int) -- te\t\r\nst123
//select * from `te\t\r\nst123`
//
//-- 换行符会当成表名的一部分, 建好表后在 dbeaver 中完全看不出来, 只能用过下面的查询验证
//create table `test
//ok`(id int) -- test换行符ok
//select * from `test
//ok`
//
//-- 第一个 '`' 是开始分割边界, 紧接着又一个 '`' 则认为是双 '`' 的第一个, 但没有又紧跟一个 '`' 报错
//create table ``test`(id int) -- error
//
//-- 第一个 '`' 是开始分割边界, 紧接着 '``' 被合并认为普通表名的 '`', 最后一个 '`' 结束分割边界
//create table ```test123`(id int) -- `test123
//
//-- 第一个 '`' 是开始分割边界, 结尾的 '``' 被合并认为普通表名的 '`', 这样就没有结束边界, 报错
//create table `test``(id int) -- error
//
//-- 第一个 '`' 是开始分割边界, 结尾的 '``' 被合并认为普通表名的 '`', 后面又跟了一个 '`' 结束分割边界
//create table `test456```(id int) -- test456`
//
//-- 第一个 '`' 是开始分割边界, 中间的 '`' 被认为是结束分割边界, 后面还有 就报错了
//create table `test`ok`(id int) -- error
//
//
//
//
//注意:
// 1. 当开启 sql_mode ANSI_QUOTES 时, 双引号(")也被认为是引用符;
// 2. 当两个引用符交错出现时, 认为首先出现的是引用符, 如: create table "t_person`withref"(id int)
#endregion

public partial class MySqlAccess : DBAccess
{
    #region 表名或标识符分析/解析
    private static readonly IReadOnlyList<string> _QuotedCharacters = ["``", "\"\""];
    /// <inheritdoc />
    public override IReadOnlyList<string> QuotedCharacters => _QuotedCharacters;
    /// <inheritdoc />
    public override string AddQuote(string pureName)
    {
        if (pureName.IsNullOrEmpty()) return pureName;
        return $"`{pureName.Replace("`", "``")}`";
    }
    /// <inheritdoc />
    public override IEnumerable<string> AnalysisQuotedName(string quotedName)
    {
        var len = quotedName?.Length ?? 0;
        if (len == 0) yield break;

        var currentIdx = 0;

        void readWhiteSpaceOrDot()
        {
            while (currentIdx < len && (char.IsWhiteSpace(quotedName[currentIdx]) || quotedName[currentIdx] == '.')) currentIdx++;
        }
        string readQuotedSeg(char endChar)
        {
            var startIdx = currentIdx;
            var end = false;
            List<int> skipIdxes = null;

            while (currentIdx < len)
            {
                if (quotedName[currentIdx] != endChar)
                {
                    //没找到结束符号
                    currentIdx++;
                    continue;
                }

                //找到了结束符号
                //需要再判断一步: 两个双引号合并成一个
                currentIdx++;
                if (currentIdx >= len || quotedName[currentIdx] != endChar)
                {
                    //不一样 不能合并
                    currentIdx--;
                    end = true;
                    break;
                }
                //两个双引号, 忽略后面的一个
                skipIdxes ??= new List<int>(8);
                skipIdxes.Add(currentIdx);
                currentIdx++;
            }

            if (!end) throw new Exception($"对象名格式错误(结尾处缺少符号: 【{endChar}】): {quotedName}");
            if (skipIdxes == null)
            {
                //没有合并 简单情况如: "testdb"."table"
                return quotedName[startIdx..currentIdx];
            }
            else
            {
                //有合并情况 如: "tes\"\"tdb"."table"
                var tmpList = new List<char>(64);
                for (int i = startIdx; i < currentIdx; i++)
                {
                    if (skipIdxes.Contains(currentIdx)) continue;
                    tmpList.Add(quotedName[i]);
                }
                return new string([.. tmpList]);
            }
        }

        string readUnQuotedSeg()
        {
            var startIdx = currentIdx;
            while (currentIdx < len)
            {
                if (quotedName[currentIdx] == '.' || char.IsWhiteSpace(quotedName[currentIdx]))
                {
                    return quotedName[startIdx..currentIdx];
                }
                currentIdx++;
            }
            return quotedName[startIdx..];
        }

        //消耗左侧空白及dot(.)
        readWhiteSpaceOrDot();
        if (currentIdx >= len) yield break;

        //读取第一次
        var curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '`')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar);
            currentIdx++;//读取停止的时候指针一定对着最后一个 ` 或 " 所以要跳一个
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
            //读取停止的时候指针一定对着 . 或空白符
        }

        readWhiteSpaceOrDot();//读取停止的时候指针一定不会对着空白符或 .
        if (currentIdx >= len) yield break;

        //读取第二次
        curChar = quotedName[currentIdx];
        if (curChar == '"' || curChar == '`')
        {
            //有引用符
            currentIdx++;
            yield return readQuotedSeg(curChar);
        }
        else
        {
            //没有引用符
            yield return readUnQuotedSeg();
        }
    }
    /// <inheritdoc />
    public override TableName ParseQuotedName(string quotedName)
    {
        if (quotedName.IsNullOrWhiteSpace()) return null;
        //基础解析
        var segArr = AnalysisQuotedName(quotedName).ToArray();
        if (segArr.IsNullOrEmpty()) throw new Exception($"解析对象名称失败: {quotedName}");

        if (segArr.Length == 1)
        {
            var name = segArr[0];
            AssertUtil.NotNullOrWhiteSpace(name);
            return new TableName(EnumDBType.MySql, name);
        }
        else
        {
            //`testdb`.`table1` or  `otherName`.`testdb`.`table1`
            //mysql 中 应该就两级
            var length = segArr.Length;
            var schemaName = segArr[length - 2];
            var tableName = segArr[length - 1];
            AssertUtil.NotNullOrWhiteSpace(schemaName);
            AssertUtil.NotNullOrWhiteSpace(tableName);
            return new TableName(
                EnumDBType.MySql,
                name: tableName,
                schemaName: schemaName,
                dataBaseName: schemaName);
        }
    }
    /// <inheritdoc />
    public override TableName CreateTableName()
    {
        return new TableName(EnumDBType.MySql);
    }
    #endregion

    /// <inheritdoc />
    public override string EscapeString(string str)
    {
        if (str.IsNullOrWhiteSpace()) return str;
        if (this.IsMySqlBehavior_SqlMode_NO_BACKSLASH_ESCAPES()) return str.Replace("'", "''");
        StringBuilder sb = null;
        int last = -1;
        for (int i = 0; i < str.Length; i++)
        {
            char c = str[i];
            switch (c)
            {
                //为什么不对 % 和 _ 进行转义?
                //因为只有在 like 下 【\%】才等同于 【%】, 而其他时候 【\%】不会认为是转义的效果
                //所以在like时特殊处理, 如: select replace(replace('%_%','_','\_'),'%','\%')
                case '\'':
                case '\\':
                case '\r':
                case '\n':
                    {
                        sb ??= new StringBuilder();
                        sb.Append(str, last + 1, i - (last + 1));
                        if (c == '\r')
                        {
                            sb.Append("\\r");
                        }
                        else if (c == '\n')
                        {
                            sb.Append("\\n");
                        }
                        else
                        {
                            sb.Append('\\').Append(c);
                        }
                        last = i;
                        break;
                    }
                default:
                    continue;
            }
        }
        sb?.Append(str, last + 1, str.Length - (last + 1));
        return sb?.ToString() ?? str;
    }
}
